package com.isoftstone.storage.file;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.codehaus.plexus.util.FileUtils;

import com.isoftstone.storage.ConnectionException;
import com.isoftstone.storage.InputData;
import com.isoftstone.storage.LazyFileOutputStream;
import com.isoftstone.storage.OutputData;
import com.isoftstone.storage.ResourceDoesNotExistException;
import com.isoftstone.storage.Storage;
import com.isoftstone.storage.StreamStorage;
import com.isoftstone.storage.TransferFailedException;
import com.isoftstone.storage.authorization.AuthorizationException;
import com.isoftstone.storage.resource.Resource;

public class FileStorage extends StreamStorage implements Storage {
	public void fillInputData(InputData inputData)
			throws TransferFailedException, ResourceDoesNotExistException {
		if (getRepository().getBasedir() == null) {
			throw new TransferFailedException(
					"Unable to operate with a null basedir.");
		}

		Resource resource = inputData.getResource();

		File file = new File(getRepository().getBasedir(), resource.getName());

		if (!file.exists()) {
			throw new ResourceDoesNotExistException("File: " + file
					+ " does not exist");
		}

		try {
			InputStream in = new BufferedInputStream(new FileInputStream(file));
			inputData.setInputStream(in);
			resource.setContentLength(file.length());
		} catch (FileNotFoundException e) {
			throw new TransferFailedException("Could not read from file: "
					+ file.getAbsolutePath(), e);
		}
	}

	public void fillOutputData(OutputData outputData)
			throws TransferFailedException {
		if (getRepository().getBasedir() == null) {
			throw new TransferFailedException(
					"Unable to operate with a null basedir.");
		}

		Resource resource = outputData.getResource();

		File file = new File(getRepository().getBasedir(), resource.getName());

		createParentDirectories(file);

		OutputStream outputStream = new BufferedOutputStream(
				new LazyFileOutputStream(file));

		outputData.setOutputStream(outputStream);
	}

	protected void openConnectionInternal() throws ConnectionException {
		if (getRepository() == null) {
			throw new ConnectionException(
					"Unable to operate with a null repository.");
		}

		if (getRepository().getBasedir() == null) {
			// This condition is possible when using wagon-file under
			// integration testing conditions.
			fireSessionDebug("Using a null basedir.");
			return;
		}

		// Check the File repository exists
		File basedir = new File(getRepository().getBasedir());
		if (!basedir.exists()) {
			if (!basedir.mkdirs()) {
				throw new ConnectionException("Repository path " + basedir
						+ " does not exist," + " and cannot be created.");
			}
		}

		if (!basedir.canRead()) {
			throw new ConnectionException("Repository path " + basedir
					+ " cannot be read");
		}
	}

	public void closeConnection() {
	}

	public boolean supportsDirectoryCopy() {
		// TODO: should we test for null basedir here?
		return true;
	}

	public void putDirectory(File sourceDirectory, String destinationDirectory)
			throws TransferFailedException, ResourceDoesNotExistException,
			AuthorizationException {
		if (getRepository().getBasedir() == null) {
			throw new TransferFailedException(
					"Unable to putDirectory() with a null basedir.");
		}

		File path = resolveDestinationPath(destinationDirectory);

		try {
			/*
			 * Done to address issue found in HP-UX with regards to "."
			 * directory references. Details found in .. WAGON-30 - wagon-file
			 * failed when used by maven-site-plugin WAGON-33 -
			 * FileWagon#putDirectory() fails in HP-UX if destinationDirectory
			 * is "."
			 * http://www.nabble.com/With-maven-2.0.2-site%3Adeploy-doesn%
			 * 27t-work-t934716.html for details. Using path.getCanonicalFile()
			 * ensures that the path is fully resolved before an attempt to
			 * create it. TODO: consider moving this to IoUtils.mkdirs()
			 */
			File realFile = path.getCanonicalFile();
			realFile.mkdirs();
		} catch (IOException e) {
			// Fall back to standard way if getCanonicalFile() fails.
			path.mkdirs();
		}

		if (!path.exists() || !path.isDirectory()) {
			String emsg = "Could not make directory '" + path.getAbsolutePath()
					+ "'.";

			// Add assistive message in case of failure.
			File basedir = new File(getRepository().getBasedir());
			if (!basedir.canWrite()) {
				emsg += "  The base directory " + basedir + " is read-only.";
			}

			throw new TransferFailedException(emsg);
		}

		try {
			FileUtils.copyDirectoryStructure(sourceDirectory, path);
		} catch (IOException e) {
			throw new TransferFailedException(
					"Error copying directory structure", e);
		}
	}

	private File resolveDestinationPath(String destinationPath) {
		String basedir = getRepository().getBasedir();

		destinationPath = StringUtils.replace(destinationPath, "\\", "/");

		File path;

		if (destinationPath.equals(".")) {
			path = new File(basedir);
		} else {
			path = new File(basedir, destinationPath);
		}

		return path;
	}

	public List<String> getFileList(String destinationDirectory)
			throws TransferFailedException, ResourceDoesNotExistException,
			AuthorizationException {
		if (getRepository().getBasedir() == null) {
			throw new TransferFailedException(
					"Unable to getFileList() with a null basedir.");
		}

		File path = resolveDestinationPath(destinationDirectory);

		if (!path.exists()) {
			throw new ResourceDoesNotExistException(
					"Directory does not exist: " + destinationDirectory);
		}

		if (!path.isDirectory()) {
			throw new ResourceDoesNotExistException("Path is not a directory: "
					+ destinationDirectory);
		}

		File[] files = path.listFiles();

		List<String> list = new ArrayList<String>(files.length);
		for (int i = 0; i < files.length; i++) {
			String name = files[i].getName();
			if (files[i].isDirectory() && !name.endsWith("/")) {
				name += "/";
			}
			list.add(name);
		}
		return list;
	}

	public boolean resourceExists(String resourceName)
			throws TransferFailedException, AuthorizationException {
		if (getRepository().getBasedir() == null) {
			throw new TransferFailedException(
					"Unable to getFileList() with a null basedir.");
		}

		File file = resolveDestinationPath(resourceName);

		if (resourceName.endsWith("/")) {
			return file.isDirectory();
		}

		return file.exists();
	}

	@Override
	public void delete(String resourceName) throws TransferFailedException,
			AuthorizationException {

		File file = new File(getRepository().getBasedir(), resourceName);

		if (!file.exists()) {
			throw new ResourceDoesNotExistException("File: " + file
					+ " does not exist");
		}
		file.delete();
	}
}
